package com.perfect.downloader

import com.example.zjx8969.myapplication.HttpRequest
import com.example.zjx8969.myapplication.await
import com.perfect.ProcessCoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.Request
import okio.ByteString
import okio.buffer
import okio.sink
import okio.source
import java.io.File
import java.util.*

object Downloader {
    private val mutex by lazy { Mutex() }
    private const val maxParallelNumber = 4
    private val pendingTaskList = LinkedList<DownloadTask>()
    private val downloadingList = ArrayList<DownloadTask>(maxParallelNumber)

    fun newTask(url: String, dir: String, fileName: String, callBack: (url: String, file: File, isSuccess: Boolean) -> Unit) {
        ProcessCoroutineScope.launch(Dispatchers.IO) {
            val md5 = ByteString.of(*url.toByteArray()).md5().utf8()
            val needDownload = mutex.withLock {
                val downloadTask = downloadingList.find { it.identifier == md5 }
                if (downloadTask != null) {
                    val dest = Array<((url: String, file: File, isSuccess: Boolean) -> Unit)?>(downloadTask.callBack.size + 1) { null }
                    System.arraycopy(downloadTask.callBack, 0, dest, 0, downloadTask.callBack.size)
                    dest[downloadTask.callBack.size] = callBack
                    downloadTask.callBack = dest
                    false
                } else {
                    pendingTaskList.add(DownloadTask(url, dir, fileName, identifier = md5, callBack = arrayOf(callBack)))
                    true
                }
            }
            if (needDownload) countDownloadingAndStart()

        }
    }

    fun cancel(url: String) {
        ProcessCoroutineScope.launch(Dispatchers.IO) {
            val identifier = ByteString.of(*url.toByteArray()).md5().utf8()
            val runningTask = mutex.withLock {
                pendingTaskList.find {
                    identifier == it.identifier
                }?.apply {
                    pendingTaskList.remove(this)
                }
                downloadingList.find { it.identifier == identifier }
            }

            if (runningTask?.job != null) {
                runningTask.job?.cancel()
                countDownloadingAndStart()
            }

        }
    }

    private suspend fun countDownloadingAndStart() {
        val downloadingSize = downloadingList.size
        val task = if (downloadingSize < maxParallelNumber && pendingTaskList.isNotEmpty()) {
            mutex.withLock {
                pendingTaskList.removeFirst()?.apply {
                    downloadingList.add(this)
                }
            }
        } else null

//        Log.d("tag", "${downloadingTask.size} left")
        task?.let {
            task.job = ProcessCoroutineScope.launch {
                downloadFile(it)
            }
        }
    }

    private suspend fun downloadFile(task: DownloadTask) {
        val dir = File(task.dir)
        if (!dir.exists()) dir.mkdirs()
        val destFile = File(dir, task.fileName)
        var isSuccess = false
        if (!destFile.exists()) {
            try {
                val downloadingFile = File(dir, task.fileName + ".download")
                val request = Request.Builder().get().url(task.url).build()
                HttpRequest.httpClient.newCall(request).await()?.apply {
                    if (isSuccessful) {
                        downloadingFile.sink().buffer().use {
                            body?.byteStream()?.source()?.use { source ->
                                it.writeAll(source)
                            }
                        }
                        isSuccess = downloadingFile.renameTo(destFile)

                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        } else {
            isSuccess = true
        }

        mutex.withLock {
            downloadingList.remove(task)
        }
        task.callBack.forEach {
            it?.invoke(task.url, destFile, isSuccess)
        }
        countDownloadingAndStart()
    }

}

class DownloadTask(
        val url: String,
        val dir: String,
        val fileName: String,
        var job: Job? = null,
        val identifier: String,
        vararg var callBack: ((url: String, file: File, isSuccess: Boolean) -> Unit)?,
)


